/* arena.cpp -- Arena class implementation; handles arena drawing and robot stepping/game state
 * arena.cpp is part of Robodrome
 *
 * Copyright (c) 2009 Alex French
 * Robodrome is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * Robodrome is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include "arena.h"
#include <time.h>
#include <math.h>
#include "world.h"

Arena::Arena(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& refXml) :
	Gtk::DrawingArea(cobject), m_refXml(refXml)
{

	srand(time(NULL));
	for (int i = 0; i < 6; i++) {
		r_color[i] = ((i + 1) & 0x1);
		g_color[i] = (((i + 1) & 0x2) >> 1);
		b_color[i] = (((i + 1) & 0x4) >> 2);
		if (!r_color[i])
			r_color[i] += 0.3;
		if (!g_color[i])
			g_color[i] += 0.3;
		if (!b_color[i])
			b_color[i] += 0.3;
	}
}

Arena::~Arena()
{
}

bool Arena::on_expose_event (GdkEventExpose* event)
{
	Glib::RefPtr<Gdk::Window> window = get_window();
	if (window) {
		Gtk::Allocation allocation = get_allocation();
		const int width = allocation.get_width();
		const int height = allocation.get_height();
		Cairo::RefPtr<Cairo::Context> cr = window->create_cairo_context();

		// invert y-axis (this puts 0,0 in the bottom left)
		cr->scale((double)width/300.0, (double)height/-300);
		cr->translate(0, -300);

		// draw background
		cr->save();
		cr->set_source_rgb(1.0, 1.0, 1.0);
		cr->rectangle(0, 0, 300, 300);
		cr->fill();
		cr->restore();

		// draw robots
		for (int i=0; i < (int)players.size(); i++) {
			Robot *robot = &players[i];
			if (robot->initialized()) {
				robot->m_weapons.draw(cr);
				cr->save();
				if (robot->get_shield()) {
					cr->set_line_width(1);
					cr->set_source_rgb(0, 0.3, 0.3);
					cr->arc(robot->get_x(), robot->get_y(), 12, 0, 2 * M_PI);
					cr->stroke_preserve();
					cr->set_source_rgb(0.3, 1, 1);
					cr->fill();
				}
				cr->set_line_width(1.5);
				if (robot->alive())
					cr->set_source_rgb(r_color[i], g_color[i], b_color[i]);
				else
					cr->set_source_rgba(0, 0, 0, 0.5);
				cr->arc(robot->get_x(), robot->get_y(), 10, 0, 2 * M_PI);
				cr->fill_preserve();
				cr->set_source_rgb(0, 0, 0);
				cr->move_to(robot->get_x(), robot->get_y());
				cr->line_to(robot->get_x() + (12 * cos(robot->get_aim() * M_PI / 180)), robot->get_y() + (12 * sin(robot->get_aim() * M_PI / 180)));
				cr->stroke();
				cr->restore();
			}
		}
	}
	return true;
}

void Arena::init(void)
{
	m_chronons = 0;
	for (int i=0; i < (int)players.size(); i++) {
		players[i].init();
		players[i].set_x(rand() % 259 + 20);
		players[i].set_y(rand() % 259 + 20);
	}
}

void Arena::error(int id, int error)
{
	std::stringstream message;

	message << "<b>Error in " << players[id].get_name() << "</b>" << std::endl;

	players[id].die("buggy");
	play_snd[DEATH_SND] = true;
	switch (error) {
		case ERR_STACK_UNDERFLOW:
			message << "Stack underflow.";
			break;
		case ERR_NOT_COMPILED:
			message << "Robot not compiled.";
			break;
		case ERR_REG_RANGE:
			message << "Register out of range.";
			break;
		case ERR_INVALID_JUMP:
			message << "Jump to invalid code location.";
			break;
		case ERR_INVALID_CHANNEL:
			message << "Invalid communication channel.";
			break;
		case ERR_NO_MISSILES:
			message << "Missiles not equipped.";
			break;
	}
	error_message->set_markup(message.str());
	error_dialog->run();
	error_dialog->hide();
}

// step world one chronon
void Arena::step_world(void)
{
	// save state information
	// static state is the same during all steps
	// tmp state is the state of the previous step
	m_static_players.clear();
	for (int i=0; i < (int)players.size(); i++) {
		m_static_players.push_back(players[i].state());
	}

	// step robots
	for (int i=0; i < (int)players.size(); i++) {
		int cycles = players[i].get_cpu_cycles();
		for (int j = 0; j < cycles; j++) {
			if (players[i].alive() && (players[i].get_energy() > 0)) {
				update_random(i);
				update_turret(i);
				switch (players[i].clock()) {
					case SUCCESS:
						break;
					case ERR_STACK_UNDERFLOW:
						this->error(i, ERR_STACK_UNDERFLOW);
						break;
					case ERR_NOT_COMPILED:
						this->error(i, ERR_NOT_COMPILED);
						break;
					case ERR_REG_RANGE:
						this->error(i, ERR_REG_RANGE);
						break;
					case SYNC:
						j = cycles;
						break;
					case ERR_INVALID_JUMP:
						this->error(i, ERR_INVALID_JUMP);
						break;
					case ERR_INVALID_CHANNEL:
						this->error(i, ERR_INVALID_CHANNEL);
						break;
					case ERR_NO_MISSILES:
						this->error(i, ERR_NO_MISSILES);
						break;
				}
			} else {
				break;
			}
		}
	}

	// per-chronon things
	for (int i=0; i < (int)players.size(); i++) {
		if (players[i].alive()) {
			// test for overload condition
			if (players[i].m_reg[ENERGY] < -200) {
				players[i].die("overloaded");
				play_snd[DEATH_SND] = true;
				break;
			}

			// add 2 energy per chronon
			players[i].m_reg[ENERGY] += 2;
			if (players[i].m_reg[ENERGY] > players[i].get_energy_max_val())
				players[i].m_reg[ENERGY] = players[i].get_energy_max_val();

			// decay shields by one unit every odd chronon
			// (officially, decay half a unit per chronon...)
			if (m_chronons % 2 != 0 && players[i].m_reg[SHIELD] > 0)
				players[i].m_reg[SHIELD]--;

			// move based on speedx/speedy
			// this behavior follows robowar behavior, but it does not make sense...
			// energy has already been spent, so why would we stop?
			if (players[i].get_energy() > 0) {
				// move based on speedx
				players[i].m_reg[X] += players[i].m_reg[SPEEDX];
				players[i].m_reg[Y] += players[i].m_reg[SPEEDY];
			}

		}
	}
	// weapons are half-stepped to improve accuracy
	this->step_weapons();
	this->step_weapons();

	// play queued sounds
	for (int i = 0; i < 8; i++) {
		if (play_snd[i] && sounds_enabled) {
			switch (i) {
				case COLLISION_SND:
					collision_snd->play();
					break;
				case DEATH_SND:
					death_snd->play();
					break;
				case GUN_SND:
					gun_snd->play();
					break;
				case HELLBORE_SND:
					hellbore_snd->play();
					break;
				case HIT_SND:
					hit_snd->play();
					break;
				case MINE_SND:
					mine_snd->play();
					break;
				case MISSILE_SND:
					missile_snd->play();
					break;
				case NUKE_SND:
					nuke_snd->play();
					break;
			}
			play_snd[i] = false;
		}
	}

	m_chronons++;
}

void Arena::step_weapons(void)
{
	// step weapons from last chronon
	for (int i=0; i < (int)players.size(); i++) {
		players[i].m_weapons.step();
		for (int j=0; j < (int)players.size(); j++) {
			if (players[i].alive() && players[j].alive()) {
				int damage = players[i].m_weapons.hit(players[j].m_reg[X], players[j].m_reg[Y]);
				if (damage)
					play_snd[HIT_SND] = true; // queue hit sound
				if (damage > players[j].m_reg[SHIELD]) {
					damage -= players[j].m_reg[SHIELD];
					players[j].m_reg[SHIELD] = 0;
				} else if (damage <= players[j].m_reg[SHIELD]) {
					players[j].m_reg[SHIELD] -= damage;
					damage = 0;
				}

				players[j].m_reg[DAMAGE] -= damage;
				if (players[j].m_reg[DAMAGE] <= 0) {
					players[j].die(players[i].get_name());
					play_snd[DEATH_SND] = true;
				}
			}
		}
	}
}

void Arena::update_random(int id)
{
	players[id].m_reg[RANDOM] = rand() % 360;
}

// set turret-related registers (radar, range, doppler, probes)
// range and doppler algorithms are copied from RoboWar, Copyright (c) 1992-2000 David Harris and Lucas Dixon
void Arena::update_turret(int id)
{
	Robot *robot = &players[id];
	int a = robot->m_reg[X];
	int b = robot->m_reg[Y];
	double m = -sin((robot->m_reg[AIM] + robot->m_reg[LOOK] + 270) % 360 * M_PI / 180);
	double n = sin((robot->m_reg[AIM] + robot->m_reg[LOOK]) * M_PI / 180);

	robot->m_reg[RANGE] = 0;
	robot->m_reg[DOPPLER] = 0;
	robot->m_reg[PROBE] = 0;
	for (int i=0; i < (int)m_static_players.size(); i++) {
		if (id != i && m_static_players.at(i).alive()) { // ignore ourself and dead robots
			int c = m_static_players.at(i).m_reg[X];
			int d = m_static_players.at(i).m_reg[Y];
			double t = m * (c - a) + n * (d - b);
			if (t > 0 && fabs((m + n) * t + a + b - c - d) < 20) { // first a crude Manhatten Metric check
				if ((m * t + a - c) * (m * t + a - c) + (n * t + b - d) * (n * t + b - d) < (RADIUSSQUARED -9 )) { // in sights
					if (robot->m_reg[RANGE] == 0 || t < robot->m_reg[RANGE]) {
						robot->m_reg[RANGE] = (int)t;
						robot->m_reg[PROBE] = i;

						int target_vx = m_static_players.at(i).m_reg[SPEEDX];
						int target_vy = m_static_players.at(i).m_reg[SPEEDY];

						double rx = a - c;
						double ry = b - d;

						double rdotv = rx * target_vx + ry * target_vy;

						double doppler = sqrt((target_vx * target_vx + target_vy * target_vy) - (rdotv * rdotv / (rx * rx + ry * ry)));

						robot->m_reg[DOPPLER] = ((rx * target_vy) - (ry * target_vx)) > 0 ? round(-doppler) : round(doppler);
					}
				}
			}
		}
	}
}
